Problem
Description
小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。 这架超级钢琴可以弹奏出$n$个音符,编号为$1$至$n$。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。 小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创出来的乐曲美妙度最
大值是多少。
Input
第一行包含四个正整数n, k, L, R。其中n为音符的个数,k为乐曲所包含的超级和弦个数,L和R分别是超级和弦所
包含音符个数的下限和上限。 接下来n行,每行包含一个整数Ai,表示按编号从小到大每个音符的美妙度。
N<=500,000
k<=500,000
-1000<=$A_i$<=1000,1<=L<=R<=N且保证一定存在满足条件的乐曲
Output
只有一个整数,表示乐曲美妙度的最大值。
Sample Input
4 3 2 3
3
2
-6
8
Sample Output
11
【样例说明】
共有5种不同的超级和弦:
音符1 ~ 2,美妙度为3 + 2 = 5
音符2 ~ 3,美妙度为2 + (-6) = -4
音符3 ~ 4,美妙度为(-6) + 8 = 2
音符1 ~ 3,美妙度为3 + 2 + (-6) = -1
音符2 ~ 4,美妙度为2 + (-6) + 8 = 4
最优方案为:乐曲由和弦1,和弦3,和弦5组成,美妙度为5 + 2 + 4 = 11。
Solution
首先考虑如果k=1的时候如何来做。当k=1时,也就是只选出来一个最大值就可以了,我们可以枚举区间的起点,每一个起点对应的一个区间长度合法的区间,从这段区间里找出来一个前缀和最大的然后减去起点的就可以了。
但是如果k>1的话,我们不能只找一个最大的。那么可以考虑维护一个大根堆,每次弹出堆中最大的元素来,弹k次。每次找出来一个最大值,然后压到堆里。每次从堆中弹出一个最大的,这个最大的还记录的一些信息:起点head,合法区间的lr,以及取最大值的位置loc,那么找出这个最大值之后计入答案,然后再将同样是head为起点,[l,loc-1]和[loc+1,r]的最大值压入堆。这样就贪心地找出了前k大的。时间复杂度$O(klogn)$。
一样给大家附上了精美的代码。。。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using namespace std;
struct HeapNode
{
int val, loc, l, r, head;
bool operator < (const HeapNode &a) const
{
return val < a.val; //这玩意不能写反
}
};
typedef struct HeapNode HN;
int N, k, l, r;
int data[MAXN], sum[MAXN];
HN f[MAXN][MAXexpo];
priority_queue <HN> que;
inline void initst()
{
for (register int i = 1; i <= N; ++i)
f[i][0].val = sum[i], f[i][0].loc = i;
for (register int j = 1; j < MAXexpo; ++j) //这边要预处理一直到MAXexpo,而不能仅仅预处理到log n
{
for (register int i = 1; i + (1 << j) - 1<= N; ++i)
{
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
}
inline HN query(int l, int r)
{
int k = log2(r - l + 1); //后来发现log2原来是个STL啊。。
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
inline void Add(int l, int r, int head)
{
if (l > r) return ;
HN now = query(l, r);
now.val -= sum[head - 1], now.l = l, now.r = r, now.head = head;
//先计算前缀和
que.push(now);
}
int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
cin >> N >> k >> l >> r;
for (register int i = 1; i <= N; ++i)
{
cin >> data[i];
sum[i] = sum[i - 1] + data[i];
}
initst();
for (register int i = 1; i + l - 1 <= N; ++i)
{
int Left = min(i + l - 1, N);
int Right = min(i + r - 1, N);
Add(Left, Right, i);
}
long long ans = 0; //一定要记得初始化赋值啊。。
while (k--)
{
HN now = que.top(); //每次弹出一个最大值
que.pop();
ans += (long long)now.val;
Add(now.l, now.loc - 1, now.head);
Add(now.loc + 1, now.r, now.head); //把区间中的最大值继续压入堆中
}
cout << ans << endl;
return 0;
}